﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Timers;
using System.Configuration;
using System.Collections;
using System.Reflection;
using System.IO;
using KegData;
using KegCommunication;

namespace KegMonitor
{
    public class PluginStatus<T>
    {
        public string Name;
        public bool IsEnabled = false;
        public T Plugin;

        public PluginStatus(T plugin, bool enabled, string name)
        {
            Name = name;
            IsEnabled = enabled;
            Plugin = plugin;
        }

        public override string ToString()
        {
            return Name;
        }
       
    }

    public class KegReader
    {
        private SerialPort _serialPort = new SerialPort();
        private KegBotStatus _status;
        private static Dictionary<KegBotStatus, string> _statusText = new Dictionary<KegBotStatus, string>(){
            {KegBotStatus.Error, "An unspecified error has occured"},
            {KegBotStatus.ErrorTimeOut, "A packet has not been received in a while.  When a packet is received, this error will be cleared.  Try pinging the KegBot to make sure it is still connected."},
            {KegBotStatus.Stopped, "The KegMonitor has been stopped by the user."},
            {KegBotStatus.Running, "The KegBot is operating normally."},
            {KegBotStatus.ErrorConnection, "Cannot communicate with the KegBot. Please check connection and settings and try again. Ensure that the KegMonitor is running."},
            {KegBotStatus.ErrorPing, "The KegBot is not responding to a ping. Please check connection and settings and try again. Ensure that the KegMonitor is running."}};

        public Timer packetTrigger;
        public Timer pingTimer;

        private int currentUserId;
        private int numTaps;

        // Thread that might be authenticating the user
        System.Threading.Thread authenticateThread = null;

        // Are we waiting for a ping response (Hello packet)?
        private bool waitingOnPingResponse = false;

        public List<PluginStatus<IListenerPlugin>> ListenerPlugins = new List<PluginStatus<IListenerPlugin>>();
        public List<PluginStatus<IAuthenticatorPlugin>> AuthenticatePlugins = new List<PluginStatus<IAuthenticatorPlugin>>();
        public PluginStatus<IAuthenticatorPlugin> DefaultAuthenticatorPlugin = null;

        TapReader[] taps;

        public int CurrentUser
        {
            get { return currentUserId; }
        }

        public string Port
        {
            get { return _serialPort.PortName; }
            set
            {
                StopReading();
                _serialPort.PortName = value;
            }
        }

        public int Taps
        {
            get { return numTaps; }
            set
            {
                numTaps = value;
                InitializeTaps();

            }
        }

        public KegBotStatus Status
        {
            get { return _status; }
            set
            {
                // Fire an event that the kegbot status has changed.
                KegBotStatus oldStatus = _status;
                _status = value;
                OnStatusChanged(new StatusEventArgs(oldStatus, _status));
            }
        }

        public static string GetStatusText(KegBotStatus stat)
        {
            string val = "";
            _statusText.TryGetValue(stat, out val);

            return val;
        }

        #region Constructors and Initialization
        public KegReader(string portName, int taps)
        {
            this.numTaps = taps;
            this.currentUserId = 0;
            this.Status = KegBotStatus.Stopped;

            // Initialize serial
            _serialPort.PortName = portName;
            _serialPort.Parity = Parity.None;
            _serialPort.StopBits = StopBits.One;
            _serialPort.BaudRate = 115200;

            // Ping timer
            pingTimer = new Timer();
            pingTimer.Interval = 5000;
            pingTimer.AutoReset = false;
            pingTimer.Elapsed += new ElapsedEventHandler(PingTimerExceeded);

            // Start and end user authentication with the pours
            this.StartPour += new PourEventHandler(AuthenticateCurrentUser);
            this.EndPour += new PourEventHandler(AbortUserAuthentication);

            // Record pours and temperatures to the datasource
            this.EndPour += new PourEventHandler(KegWriter.OnPour);
            this.TemperatureChange += new TemperatureEventHandler(KegWriter.OnTemperature);

            // Send pours to any listeners using named pipes
            this.StartPour += new PourEventHandler(NotifyStartPour);
            this.ContinuePour += new PourEventHandler(NotifyContinuePour);
            this.EndPour += new PourEventHandler(NotifyEndPour);
            this.TemperatureChange += new TemperatureEventHandler(NotifyUpdateTemperature);

            // Send user authentication messages across the pipe, and to the kegwriter
            this.UserAuthenticated += new UserAuthenticatedHandler(NotifyUserAuthenticated);
            this.UserAuthenticated += new UserAuthenticatedHandler(KegWriter.OnUserAuthenticated);
            
            // Response to ping requests
            this.Hello += new HelloEventHandler(ReceivedHelloPacket);

            // Plugin callbacks
            // TODO: Add plugin framework, and each eventhandler here.
            // Load plugins
            //LoadPlugins();
            //ConnectPlugins();

            InitializeTaps();
        }

        private void LoadPlugins()
        {
            string location = System.IO.Path.GetFullPath(".\\plugins");

            if (System.IO.Directory.Exists(location))
            {
                DirectoryInfo di = new DirectoryInfo(location);
                FileInfo[] pluginFiles = di.GetFiles("*.dll");

                foreach (FileInfo fi in pluginFiles)
                {
                    LoadPlugin(fi.FullName);
                }
            }
        }

        private void LoadPlugin(string assemblyPath)
        {
            try
            {
                // Load all of the binaries in the plugin folder
                Assembly assembly = Assembly.LoadFile(assemblyPath);
                Type type = assembly.GetType("KegPlugin.Plugin");

                if (type != null)
                {
                    // Try to load as either a listener or a authenticator plugin (possibly both)
                    IListenerPlugin iplugin = Activator.CreateInstance(type) as IListenerPlugin;
                    IAuthenticatorPlugin aplugin = Activator.CreateInstance(type) as IAuthenticatorPlugin;

                    if (iplugin != null)
                    {
                        PluginStatus<IListenerPlugin> i = new PluginStatus<IListenerPlugin>(iplugin, false, assembly.FullName);
                        ListenerPlugins.Add(i);
                    }
                    if (aplugin != null)
                    {
                        PluginStatus<IAuthenticatorPlugin> a = new PluginStatus<IAuthenticatorPlugin>(aplugin, false, assembly.FullName);
                        AuthenticatePlugins.Add(a);
                    }
                }
            }
            catch (TypeLoadException tle)
            {
                KegLog.LogWarning(assemblyPath + " does not implement IListenerPlugin or IAuthenticatorPlugin.");
            }
        }

        public void ConnectPlugin(PluginStatus<IListenerPlugin> iplugin)
        {
            if(iplugin.IsEnabled == true)
            {
                this.PourPlugin += new PourPluginEventHandler(iplugin.Plugin.Pour);
                this.TemperaturePlugin += new TemperaturePluginEventHandler(iplugin.Plugin.Temperature);
            }
            else
            {
                this.PourPlugin -= new PourPluginEventHandler(iplugin.Plugin.Pour);
                this.TemperaturePlugin -= new TemperaturePluginEventHandler(iplugin.Plugin.Temperature);
            }
        }

        public void ConnectPlugin(PluginStatus<IAuthenticatorPlugin> aplugin)
        {
            if (aplugin.IsEnabled == true)
            {
                DefaultAuthenticatorPlugin = aplugin;
            }
            else
            {
                
            }
        }

        private void ConnectPlugins()
        {
            // Register each of the callbacks on the plugin
            foreach (PluginStatus<IListenerPlugin> p in ListenerPlugins)
            {
                 ConnectPlugin(p);
            }
        }

        private void StartWatchdog()
        {
            packetTrigger = new Timer();
            packetTrigger.AutoReset = false;
            packetTrigger.Elapsed += this.CommunicationError;

            ResetWatchdog();
        }

        private void ResetWatchdog()
        {
            if (packetTrigger != null)
            {
                packetTrigger.Stop();
                packetTrigger.Interval = Properties.Settings.Default.WatchDogTimeout;
                packetTrigger.Start();
            }
            else
            {
                StartWatchdog();
            }
            Status = KegBotStatus.Running;
        }

        private void StopWatchDog()
        {
            if (packetTrigger != null)
            {
                packetTrigger.Stop();
            }
        }

        private void InitializeTaps()
        {
            // Create the appropriate number of taps
            taps = new TapReader[numTaps];

            // Initialize each tap
            for (byte i = 0; i < taps.Length; i++)
            {
                // Keep a reference to this kegmonitor for firing events
                taps[i] = new TapReader(this);

                // Reset all pour information
                taps[i].Reset();

                // Set the position (for channels)
                taps[i].position = (byte)(i);
            }
        }

        #endregion

        #region EventHandlers

        public event PourPluginEventHandler PourPlugin;
        public virtual void OnPourPlugin(Pour p)
        {
            if (PourPlugin != null)
            {
                PourPlugin(p);
            }
        }

        public event TemperaturePluginEventHandler TemperaturePlugin;
        public virtual void OnTemperaturePlugin(Temperature t)
        {
            if (TemperaturePlugin != null)
            {
                TemperaturePlugin(t);
            }
        }

        public event StatusEventHandler StatusChange;
        public virtual void OnStatusChanged(StatusEventArgs e)
        {
            if (StatusChange != null)
            {
                StatusChange(this, e);
            }
        }

        public event PacketEventHandler Packet;
        public virtual void OnPacket(PacketEventArgs e)
        {
            if (Packet != null)
            {
                Packet(this, e);
            }
        }

        public event HelloEventHandler Hello;
        public virtual void OnHello(HelloEventArgs e)
        {
            if (Hello != null)
            {
                Hello(this, e);
            }
        }

        public event PourEventHandler StartPour;
        public virtual void OnStartPour(PourEventArgs e)
        {
            if (StartPour != null)
            {
                StartPour(this, e);
            }
        }

        public event PourEventHandler ContinuePour;
        public virtual void OnContinuePour(PourEventArgs e)
        {
            if (ContinuePour != null)
            {
                ContinuePour(this, e);
            }
        }

        public event PourEventHandler EndPour;
        public virtual void OnEndPour(PourEventArgs e)
        {
            if (EndPour != null)
            {
                EndPour(this, e);
            }
        }

        public event UserAuthenticatedHandler UserAuthenticated;
        public virtual void OnUserAuthenticated(UserAuthenticatedEventArgs e)
        {
            if (UserAuthenticated != null)
            {
                UserAuthenticated(this, e);
            }
        }

        public event TemperatureEventHandler TemperatureChange;
        public virtual void OnTemperatureChanged(TemperatureEventArgs e)
        {
            if (TemperatureChange != null)
            {
                TemperatureChange(this, e);
            }
        }

        #endregion

        public void StopReading()
        {
            // Remove the callback for receiving data
            _serialPort.DataReceived -= new SerialDataReceivedEventHandler(serialDataRecieved);

            // Close the port
            _serialPort.Close();
        }

        public bool StartReading()
        {
            bool success = false;

            for (int attempts = 1; attempts <= Properties.Settings.Default.PortRetryAttempts; attempts++)
            {
                KegLog.LogComment("Opening " + _serialPort.PortName + " (try " + attempts + "/" + Properties.Settings.Default.PortRetryAttempts + ")");
                try
                {
                    // Open the port and create a callback for whenever a packet is received.
                    _serialPort.Open();
                    _serialPort.DataReceived += new SerialDataReceivedEventHandler(serialDataRecieved);
                    Status = KegBotStatus.Running;
                    
                    // Start the packet watchdog timer
                    StartWatchdog();

                    success = true;
                    break;
                }
                catch (Exception e)
                {
                    // Serial port is not available
                    Status = KegBotStatus.ErrorConnection;
                    int wait = Properties.Settings.Default.PortRetryDelay;
                    KegLog.LogComment(e.Message + "  Trying again in " + wait / 1000 + " seconds.");

                    System.Threading.Thread.Sleep(wait);
                }
            }

            return success;
        }

        public void SendMessage(byte[] message)
        {
            // Send the packet
            if (_serialPort.IsOpen)
            {
                _serialPort.Write(message, 0, message.Length);
            }
        }

        public bool Ping()
        {
            return this.Ping(5000);
        }

        public bool Ping(int timer)
        {
            // Send a ping packet to the KegBot
            waitingOnPingResponse = true;

            pingTimer.Interval = timer;
            pingTimer.Start();

            // TODO: This is a well formed packet, but it carries junk data (I stole a HELLO packet and changed the packet type)
            // This might be able to be shortened.
            // TODO: Get the header and footer data from the KegPacket class.
            byte[] pingPacket = { 0x4b, 0x42, 0x53, 0x50, 0x20, 0x76, 0x31, 0x3a, KegPacket.KB_MESSAGE_TYPE_PING, 0x00, 0x04, 0x00, 0x01, 0x02, 0x05, 0x00, 0xfe, 0x00, 0x0d, 0x0a};
            SendMessage(pingPacket);
            
            // Notify whether the response was recieved
            return !waitingOnPingResponse;
        }

        #region Test Packets
        private byte[] BuildTemperaturePacket(string OneWireId, double Value)
        {
            byte length = 47;
            byte[] packet = new byte[length];

            // Copy the header into the packet
            int bytesWritten = 0;
            Array.Copy(KegPacket.KBSP_HEADER, packet, KegPacket.KBSP_HEADER.Length);
            bytesWritten = KegPacket.KBSP_HEADER.Length;

            packet[bytesWritten++] = KegPacket.KB_MESSAGE_TYPE_THERMO_READING;
            packet[bytesWritten++] = length;
            packet[bytesWritten++] = 0;
            packet[bytesWritten++] = 0;
            packet[bytesWritten++] = KegPacket.KB_MESSAGE_TYPE_THERMO_READING_TAG_SENSOR_NAME;
            packet[bytesWritten++] = 0x17;
            packet[bytesWritten++] = 0x74; // t
            packet[bytesWritten++] = 0x68; // h
            packet[bytesWritten++] = 0x65; // e
            packet[bytesWritten++] = 0x72; // r
            packet[bytesWritten++] = 0x6d; // m
            packet[bytesWritten++] = 0x6f; // o
            packet[bytesWritten++] = 0x2d; // -
            
            // Copy 16 ASCII bytes of the name to the packet
            Array.Copy(Helper.StrToByteArray(OneWireId), 0, packet, bytesWritten, 16);
            bytesWritten += 16;

            // should be 29
            packet[bytesWritten++] = KegPacket.KB_MESSAGE_TYPE_THERMO_READING_TAG_SENSOR_READING;
            packet[bytesWritten++] = 0x4;


            // Add the value to the packet
            int value = Convert.ToInt32(Value * 1000000.0);
            Array.Copy(BitConverter.GetBytes(value), 0, packet, bytesWritten, 4);

            // Copy the footer onto the end of the packet
            Array.Copy(KegPacket.KBSP_FOOTER, 0, packet, packet.Length - KegPacket.KBSP_FOOTER.Length, KegPacket.KBSP_FOOTER.Length);

            return packet;
        }

        private byte[] BuildPourPacket(byte Position, double Volume, int UserId)
        {
            byte length = 27;
            byte[] packet = new byte[length];

            // Copy the header into the packet
            int bytesWritten = 0;
            Array.Copy(KegPacket.KBSP_HEADER, packet, KegPacket.KBSP_HEADER.Length);
            bytesWritten = KegPacket.KBSP_HEADER.Length;

            packet[bytesWritten++] = KegPacket.KB_MESSAGE_TYPE_METER_STATUS;
            packet[bytesWritten++] = length;
            packet[bytesWritten++] = 0;
            packet[bytesWritten++] = 0;
            packet[bytesWritten++] = KegPacket.KB_MESSAGE_TYPE_METER_STATUS_TAG_METER_NAME;
            packet[bytesWritten++] = 0x05;
            packet[bytesWritten++] = 0x66; // f
            packet[bytesWritten++] = 0x6c; // l
            packet[bytesWritten++] = 0x6f; // o
            packet[bytesWritten++] = 0x77; // w
            packet[bytesWritten++] = (byte)(0x30 + Position);
            packet[bytesWritten++] = KegPacket.KB_MESSAGE_TYPE_METER_STATUS_TAG_METER_READING;
            packet[bytesWritten++] = 0x00;

            // Add the value to the packet
            int value = Convert.ToInt32(Volume * Properties.Settings.Default.FlowTicksPerLiter);
            Array.Copy(BitConverter.GetBytes(value), 0, packet, bytesWritten, 4);

            // Copy the footer onto the end of the packet
            Array.Copy(KegPacket.KBSP_FOOTER, 0, packet, packet.Length - KegPacket.KBSP_FOOTER.Length, KegPacket.KBSP_FOOTER.Length);

            return packet;
        }

        private class PourThreadData
        {
            public byte Position;
            public double Volume;
            public int UserId;
            public int PacketCount;
            public int PacketDelay;

            public PourThreadData(byte position, double volume, int userid, int packetcount, int delay)
            {
                Position = position;
                Volume = volume;
                UserId = userid;
                PacketCount = packetcount;
                PacketDelay = delay;
            }
        }

        public void SendTestPour(byte Position, double Volume, int UserId, int PacketCount, int TimeBetweenPackets)
        {
            System.Threading.Thread pourThread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(SendTestPourThread));
            
            PourThreadData data = new PourThreadData(Position, Volume, UserId, PacketCount, TimeBetweenPackets);
            pourThread.Start(data);
        }

        private void SendTestPourThread(Object obj)
        {
            PourThreadData ptd = obj as PourThreadData;
            // Send the test pour as a series of increasing volumes
            double volumeDelta = ptd.Volume / (ptd.PacketCount - 1);
            double current = 0;

            for (int i = 0; i < ptd.PacketCount; i++)
            {
                byte[] packet = BuildPourPacket(ptd.Position, current, ptd.UserId);
                ParsePacket(packet);
                System.Threading.Thread.Sleep(ptd.PacketDelay);
                current += volumeDelta;
            }
        }

        public void SendTestTemperature(string OneWireId, double value)
        {
            byte[] packet = BuildTemperaturePacket(OneWireId, value);
            ParsePacket(packet);
        }
#endregion

        #region Communication Events
        private void CommunicationError(object sender, ElapsedEventArgs e)
        {
            KegLog.LogWarning("KegBot Watchdog timeout.  Have not received a packet in 30 seconds");
            Status = KegBotStatus.ErrorTimeOut;
            StopWatchDog();
        }

        private void PingBack(object sender, HelloEventArgs e)
        {
            if (waitingOnPingResponse)
            {
                Status = KegBotStatus.Running;
            }
            waitingOnPingResponse = false;
            pingTimer.Stop();
        }

        private void ReceivedHelloPacket(object sender, HelloEventArgs e)
        {
            // If we were waiting on a paacket, mark waiting as false
            KegLog.LogComment(String.Format("Hello! from KegBot Firmware version {0}", e.Version));
            waitingOnPingResponse = false;
            pingTimer.Stop();
        }

        private void PingTimerExceeded(object sender, ElapsedEventArgs e)
        {
            waitingOnPingResponse = false;
            Program.KegBot.Status = KegBotStatus.ErrorPing;
            StopWatchDog();
        }
        #endregion

        #region Packet handling
        public void ParsePacket(byte[] packet)
        {
            // Verify that the packet has the appropriate header and footer
            if (packet[0] == (byte)'K' &&
                packet[1] == (byte)'B' &&
                packet[2] == (byte)'S' &&
                packet[3] == (byte)'P' &&
                packet[4] == (byte)' ' &&
                packet[5] == (byte)'v' &&
                packet[6] == (byte)'1' &&
                packet[7] == (byte)':' &&
                packet[packet.Length - 2] == (byte)'\r' &&
                packet[packet.Length - 1] == (byte)'\n')
            {

                // Received a packet, reset the watchdog timer
                ResetWatchdog();

                // Copy just the data into the new packet (without header or footer)
                byte[] data = new byte[packet.Length - 10];
                Array.Copy(packet, 8, data, 0, packet.Length - 10);

                // Fire an event that packet was received.
                this.OnPacket(new PacketEventArgs(data, (PacketType)data[0]));

                switch (data[0])
                {
                    case (byte)PacketType.Hello:
                        // Hello packets happen when the kegbot is reset
                        // and in response to Pings
                        HelloPacket hp = new HelloPacket();
                        if (hp.ParsePacket(data) == true)
                        {
                            // Send an event that a hello packet was received
                            this.OnHello(new HelloEventArgs(hp.Version));
                        }
                        break;
                    case (byte)PacketType.FlowMeter:
                        // Parse the flow meter packet
                        FlowMeterPacket flp = new FlowMeterPacket();
                        if (flp.ParsePacket(data) == true)
                        {
                            // Make sure that the channel is appropriate
                            if (flp.channel < numTaps)
                            {
                                // Collect the pour data
                                taps[flp.channel].StartOrContinuePour(flp.ticks / Properties.Settings.Default.FlowTicksPerLiter);
                            }
                            else
                            {
                                // Recieved a tap that this reader does not know about
                                //Status = KegBotStatus.Error;
                                //StopReading();
                            }
                        }

                        break;
                    case (byte)PacketType.Thermometer:
                        // Got a temperature packet
                        TemperaturePacket tempPacket = new TemperaturePacket();
                        if (tempPacket.ParsePacket(data) == true)
                        {
                            TemperatureEventArgs tempEvent = new TemperatureEventArgs(tempPacket.addressString, tempPacket.value, DateTime.Now);
                            this.OnTemperatureChanged(tempEvent);
                        }
                        break;
                    case (byte)PacketType.Presence:
                    case (byte)PacketType.OutputStatus:
                        // TODO: Implement
                        throw new NotImplementedException();
                        break;
                    default:
                        // Unknown packet
                        //Status = KegBotStatus.Error;
                        //StopReading();
                        break;
                }
            }
        }

        private void serialDataRecieved(object sender, SerialDataReceivedEventArgs e)
        {
            // Get the whole packet from the serial port
            byte[] packet = new byte[255];
            byte[] formedpacket = null; // new byte[255];

            for (int i = 0; i < packet.Length; i++)
            {
                _serialPort.Read(packet, i, 1);

                if (i >= 2)
                {
                    if (packet[i - 1] == (byte)'\r' &&
                        packet[i] == (byte)'\n')
                    {
                        // i is not the last position in the packet

                        // find the begining
                        for (int j = 0; j < packet.Length - 8; j++)
                        {
                            if (packet[j] == (byte)'K' &&
                                packet[j + 1] == (byte)'B' &&
                                packet[j + 2] == (byte)'S' &&
                                packet[j + 3] == (byte)'P' &&
                                packet[j + 4] == (byte)' ' &&
                                packet[j + 5] == (byte)'v' &&
                                packet[j + 6] == (byte)'1' &&
                                packet[j + 7] == (byte)':')
                            {
                                // Copy data into buffer
                                int end = i;
                                int start = j;
                                int length = (end - start) + 1;
                                formedpacket = new byte[length];
                                Array.Copy(packet, start, formedpacket, 0, length );

                                // Parse the packet with header and footer
                                ParsePacket(formedpacket);

                                break;
                            }
                        }
                        break;
                    }
                }
            }
        }
        #endregion

        private void AuthenticateCurrentUser(object sender, PourEventArgs e)
        {   
            // Spawn a worker thread in order to capture the image and do any processing
            //new System.Threading.Thread(new System.Threading.ThreadStart(CaptureCameraImage)).Start();
            authenticateThread = new System.Threading.Thread(new System.Threading.ThreadStart(BeginUserAuthentication));
            authenticateThread.Start();
        }

        private void AbortUserAuthentication(object sender, PourEventArgs e)
        {
            // Un-gracefully kill the thread that was performing the user authentication
            // TODO: Make this more graceful so the authentication plugin can cleanup
            if (authenticateThread != null && authenticateThread.IsAlive)
            {
                authenticateThread.Abort();
                authenticateThread = null;
            }
        }

        private void BeginUserAuthentication()
        {
            // Get the default user
            int currentUserId = KegWriter.GetDefaultUserId();

            // Notify the UI process that the default user is being used
            UserAuthenticatedEventArgs uaea = new UserAuthenticatedEventArgs(currentUserId);
            this.OnUserAuthenticated(uaea);

            // Use the default authentication plugin to get the current User.
            // If this doesn't finish by the time that the pour is finished, then the guest user is used
            if (DefaultAuthenticatorPlugin != null)
            {
                currentUserId = DefaultAuthenticatorPlugin.Plugin.AuthenticateUser();

                if (currentUserId != -1)
                {
                    // Notify the UI process of the new user that was authenticated
                    uaea = new UserAuthenticatedEventArgs(currentUserId);
                    this.OnUserAuthenticated(uaea);
                }
            } 
        }

        #region Notification Methods
        private void NotifyStartPour(object sender, PourEventArgs e)
        {
            KegCommunication.Sender.NotifyStartPour(e);
        }

        private void NotifyContinuePour(object sender, PourEventArgs e)
        {
            KegCommunication.Sender.NotifyContinuePour(e);
        }

        private void NotifyEndPour(object sender, PourEventArgs e)
        {
            KegCommunication.Sender.NotifyEndPour(e);
        }

        private void NotifyUpdateTemperature(object sender, TemperatureEventArgs e)
        {
            KegCommunication.Sender.NotifyUpdateTemperature(e);
        }

        private void NotifyUserAuthenticated(object sender, UserAuthenticatedEventArgs e)
        {
            KegCommunication.Sender.NotifyUserAuthenticated(e);
        }
        #endregion
    }
}